package wechat

import (
	"context"
	"encoding/xml"
	"fmt"
	"net/http"
	"product/common/nets"
)

// RedBase 红包基本信息
type RedBase struct {
	XMLName   xml.Name `xml:"xml"`
	NonceStr  *CDATA   `xml:"nonce_str"`  // 随机字符串 name:nonce_str
	Sign      *CDATA   `xml:"sign" `      // 签名 name:sign
	MchBillNo *CDATA   `xml:"mch_billno"` // 商户订单号 name:mch_billno
	MchId     *CDATA   `xml:"mch_id"`     // 商户号 name:mch_id
}

// RedEnvelopeRespBase 红包基本返回相应
type RedEnvelopeRespBase struct {
	XMLName xml.Name `xml:"xml"`
	// SUCCESS/FAIL 此字段是通信标识，非红包发放结果标识，红包发放是否成功需要查看result_code来判断
	ReturnCode *CDATA `xml:"return_code,omitempty"`
	// 返回信息，如非空，为错误原因
	ReturnMsg *CDATA `xml:"return_msg,omitempty"`
	// SUCCESS/FAIL 在ReturnCode 为 SUCCESS 时返回，且ResultCode为FAIL时返回，
	// 请务必再请求一次查询接口
	ResultCode *CDATA `xml:"result_code,omitempty"`
	// 错误代码
	ErrCode *CDATA `xml:"err_code,omitempty"`
	// 错误代码描述
	ErrCodeDes *CDATA `xml:"err_code_des,omitempty"`
	// 商户订单号
	MchBillNo *CDATA `xml:"mch_billno,omitempty"`
	// 商户号
	MchId string `xml:"mch_id,omitempty"`
}

// RedEnvelope 红包
type RedEnvelope struct {
	RedBase
	WxAppId     *CDATA `xml:"wxappid"`      // 公众账号appid小程序appid 公众账号appid
	SendName    *CDATA `xml:"send_name"`    // 红包发送者名称 name:send_name
	ReOpenId    *CDATA `xml:"re_openid"`    // 用户openid name:re_openid
	TotalAmount *CDATA `xml:"total_amount"` // 红包总金额 单位分 name:total_amount
	TotalNum    *CDATA `xml:"total_num"`    // 红包数量 name:total_num
	Wishing     *CDATA `xml:"wishing"`      // 红包祝福语 name:wishing
	ActName     *CDATA `xml:"act_name"`     // 活动名称 name:act_name
	Remark      *CDATA `xml:"remark"`       // 备注 name:remark
	NotifyWay   *CDATA `xml:"notify_way"`   // 通知方式 name:notify_way
	ClientIp    *CDATA `xml:"client_ip"`    // 客户端IP

	// 发放红包使用场景，红包金额大于200时必传
	//PRODUCT_1:商品促销
	//PRODUCT_2:抽奖
	//PRODUCT_3:虚拟物品兑奖
	//PRODUCT_4:企业内部福利
	//PRODUCT_5:渠道分润
	//PRODUCT_6:保险回馈
	//PRODUCT_7:彩票派奖
	//PRODUCT_8:税务刮奖
	SceneId *CDATA `xml:"scene_id,omitempty"` // 场景id ,omitempty
}

// RedEnvelopeQuery 查询红包
type RedEnvelopeQuery struct {
	RedBase
	AppId    *CDATA `xml:"appid"`     // 公众账号appid小程序appid 公众账号appid
	BillType *CDATA `xml:"bill_type"` // 订单类型 name:bill_type
}

// RedEnvelopeResp 红包返回相应
type RedEnvelopeResp struct {
	RedEnvelopeRespBase
	// 公众号appid小程序appid
	WxAppId *CDATA `xml:"wxappid,omitempty"`
	// 用户openid
	ReOpenId *CDATA `xml:"re_openid,omitempty"`
	// 付款金额
	TotalAmount int `xml:"total_amount,omitempty"`
	// 微信单号
	SendListId *CDATA `xml:"send_listid,omitempty"`
}

// RedMimiEnvelopeResp 小程序红包返回相应
type RedMimiEnvelopeResp struct {
	RedEnvelopeResp
	// jsapi入参
	Package *CDATA `xml:"package,omitempty"`
}

type RedEnvelopeQueryResp struct {
	RedEnvelopeRespBase
	// SENDING:发放中
	// SENT:已发放待领取
	// FAILED：发放失败
	// RECEIVED:已领取
	// RFUND_ING:退款中
	// REFUND:已退款
	// 红包状态
	Status *CDATA `xml:"status,omitempty"`
	// 发放类型
	SendType *CDATA `xml:"send_type,omitempty"`
	// 红包类型
	HbType *CDATA `xml:"hb_type,omitempty"`
	// 红包数量
	TotalNum *CDATA `xml:"total_num,omitempty"`
	// 红包金额
	TotalAmount *CDATA `xml:"total_amount,omitempty"`
	// 失败原因
	Reason *CDATA `xml:"reason,omitempty"`
	// 红包发送时间
	SendTime *CDATA `xml:"send_time,omitempty"`
	// 红包退回时间
	RefundTime *CDATA `xml:"refund_time,omitempty"`
	// 退款金额
	RefundAmount *CDATA `xml:"refund_amount,omitempty"`
	// 祝福语
	Wishing *CDATA `xml:"wishing,omitempty"`
	// 活动描述
	Remark *CDATA `xml:"remark,omitempty"`
	// 领红包Openid
	OpenId *CDATA `xml:"openid,omitempty"`
	// 领取红包金额
	Amount *CDATA `xml:"amount,omitempty"`
	// 领取红包时间
	ReceiveTime *CDATA `xml:"rcv_time,omitempty"`
}

// ToXmlString 转换为xml字符串
func (red *RedEnvelope) ToXmlString() (string, error) {
	xmlBytes, err := xml.Marshal(red)
	if err != nil {
		return "", err
	}
	return string(xmlBytes), nil
}

// SendRedEnvelope 发送红包
// certPem:证书路径
// appKey:appKey
// certKeyPem:证书密钥
func SendRedEnvelope(ctx context.Context,
	appKey, certPem, certKeyPem string,
	red *RedEnvelope) (resp *RedEnvelopeResp,
	err error) {
	plainText := splicingCheckInput(appKey, red)
	sign := CreateWeChatMd5(plainText)
	red.Sign = &CDATA{Value: sign}
	xmlStr, err := red.ToXmlString()
	if err != nil {
		return nil, err
	}
	cert, err := GetPemCert(certPem, certKeyPem)
	if err != nil {
		return nil,
			fmt.Errorf("get pem cert error:%v", err)
	}
	req := nets.NewWebHttpReq()
	rsp, err := req.SetContext(ctx).
		SetUrl(SendRedPack.GetFullApiUrl(China)).
		SetHeader("Content-Type", "application/xml").
		SetMethod(http.MethodPost).
		SetTls(cert).
		SetBody(xmlStr).
		Send()
	if err != nil {
		return nil,
			fmt.Errorf("send red pack error:%v", err)
	}
	strXml := req.ParseResponseToString(rsp)
	err = xml.Unmarshal([]byte(strXml), &resp)
	if err != nil {
		return nil,
			fmt.Errorf("parse red pack response error:%v", err)
	}
	return
}

// SendRedEnvelopePfx 发送红包
// pfx:pfx证书路径
// appKey:appKey
func SendRedEnvelopePfx(ctx context.Context,
	pfx, appKey string,
	red *RedEnvelope) (resp *RedEnvelopeResp,
	err error) {
	plainText := splicingCheckInput(appKey, red)
	sign := CreateWeChatMd5(plainText)
	red.Sign = &CDATA{Value: sign}
	xmlStr, err := red.ToXmlString()
	if err != nil {
		return nil, fmt.Errorf("to xml string error:%v", err)
	}
	tls, err := GetPks12Cert(pfx, appKey)
	req := nets.NewWebHttpReq()
	rsp, err := req.SetContext(ctx).
		SetUrl(SendRedPack.GetFullApiUrl(China)).
		SetHeader("Content-Type", "application/xml").
		SetMethod(http.MethodPost).
		SetTls(tls).
		SetBody(xmlStr).
		Send()
	if err != nil {
		return nil,
			fmt.Errorf("send red pack error:%v", err)
	}
	strXml := req.ParseResponseToString(rsp)
	err = xml.Unmarshal([]byte(strXml), &resp)
	if err != nil {
		return nil,
			fmt.Errorf("parse red pack response error:%v", err)
	}
	return
}

// SendMiniProgramRedEnvelopePfx 发送小程序红包
// pfx:pfx证书路径
// appKey:appKey
func SendMiniProgramRedEnvelopePfx(ctx context.Context,
	pfx, appKey string,
	red *RedEnvelope) (resp *RedMimiEnvelopeResp,
	err error) {
	plainText := splicingCheckInput(appKey, red)
	sign := CreateWeChatMd5(plainText)
	red.Sign = &CDATA{Value: sign}
	xmlStr, err := red.ToXmlString()
	if err != nil {
		return nil, fmt.Errorf("to xml string error:%v", err)
	}
	tls, err := GetPks12Cert(pfx, appKey)
	req := nets.NewWebHttpReq()
	rsp, err := req.SetContext(ctx).
		SetUrl(AppletRed.GetFullApiUrl(China)).
		SetHeader("Content-Type", "application/xml").
		SetMethod(http.MethodPost).
		SetTls(tls).
		SetBody(xmlStr).
		Send()
	if err != nil {
		return nil,
			fmt.Errorf("send red pack error:%v", err)
	}
	strXml := req.ParseResponseToString(rsp)
	err = xml.Unmarshal([]byte(strXml), &resp)
	if err != nil {
		return nil,
			fmt.Errorf("parse red pack response error:%v", err)
	}
	return
}

// SendMiniProgramRedEnvelope 发送小程序红包
// certPem:证书路径
// appKey:appKey
// certKeyPem:证书密钥
func SendMiniProgramRedEnvelope(ctx context.Context,
	appKey, certPem, certKeyPem string,
	red *RedEnvelope) (resp *RedMimiEnvelopeResp,
	err error) {
	plainText := splicingCheckInput(appKey, red)
	sign := CreateWeChatMd5(plainText)
	red.Sign = &CDATA{Value: sign}
	xmlStr, err := red.ToXmlString()
	if err != nil {
		return nil, err
	}
	cert, err := GetPemCert(certPem, certKeyPem)
	if err != nil {
		return nil,
			fmt.Errorf("get pem cert error:%v", err)
	}
	req := nets.NewWebHttpReq()
	rsp, err := req.SetContext(ctx).
		SetUrl(AppletRed.GetFullApiUrl(China)).
		SetHeader("Content-Type", "application/xml").
		SetMethod(http.MethodPost).
		SetTls(cert).
		SetBody(xmlStr).
		Send()
	if err != nil {
		return nil,
			fmt.Errorf("send red pack error:%v", err)
	}
	strXml := req.ParseResponseToString(rsp)
	err = xml.Unmarshal([]byte(strXml), &resp)
	if err != nil {
		return nil,
			fmt.Errorf("parse red pack response error:%v", err)
	}
	return
}

// QueryRedEnvelope 查询红包发放结果
func QueryRedEnvelope(ctx context.Context,
	query *RedEnvelopeQuery,
	pemFile, pemKey string) (queryResp *RedEnvelopeQueryResp,
	err error) {
	plainTxt := splicingQueryRebInput(query)
	sign := CreateWeChatMd5(plainTxt)
	query.RedBase.Sign = &CDATA{Value: sign}
	xmlStr, err := xml.Marshal(query)
	if err != nil {
		return nil, err
	}
	cert, err := GetPemCert(pemFile, pemKey)
	if err != nil {
		return nil,
			fmt.Errorf("get pem cert error:%v", err)
	}
	req := nets.NewWebHttpReq()
	rsp, err := req.SetContext(ctx).
		SetUrl(RedPackQuery.GetFullApiUrl(China)).
		SetHeader("Content-Type", "application/xml").
		SetMethod(http.MethodPost).
		SetTls(cert).
		SetBody(xmlStr).
		Send()
	if err != nil {
		return nil,
			fmt.Errorf("send red pack error:%v", err)
	}
	strXml := req.ParseResponseToString(rsp)
	err = xml.Unmarshal([]byte(strXml), &queryResp)
	if err != nil {
		return nil,
			fmt.Errorf("parse red pack response error:%v", err)
	}
	return
}

// **********************************************************************************************************************
// 私有方法
// **********************************************************************************************************************

func splicingCheckInput(appKey string,
	red *RedEnvelope) (plainTxt string) {
	signMap := make(map[string]any)
	signMap["nonce_str"] = red.NonceStr.Value
	signMap["mch_billno"] = red.MchBillNo.Value
	signMap["mch_id"] = red.MchId.Value
	signMap["wxappid"] = red.WxAppId.Value
	signMap["send_name"] = red.SendName.Value
	signMap["re_openid"] = red.ReOpenId.Value
	signMap["total_amount"] = red.TotalAmount.Value
	signMap["total_num"] = red.TotalNum.Value
	signMap["wishing"] = red.Wishing.Value
	signMap["client_ip"] = red.ClientIp.Value
	signMap["act_name"] = red.ActName.Value
	signMap["remark"] = red.Remark.Value
	signMap["notify_way"] = red.NotifyWay.Value
	return SortedMapToString(signMap, appKey)
}

// splicingQueryRebInput 查询红包参数
func splicingQueryRebInput(param *RedEnvelopeQuery) (plainTxt string) {
	signMap := make(map[string]any)
	signMap["nonce_str"] = param.NonceStr.Value
	signMap["mch_billno"] = param.MchBillNo.Value
	signMap["mch_id"] = param.MchId.Value
	signMap["bill_type"] = param.BillType.Value
	signMap["appid"] = param.AppId.Value
	return SortedMapToString(signMap, param.AppId.Value)
}
